package org.hamster.common.puremvc.events
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	
	import mx.collections.ArrayCollection;
	import mx.collections.ArrayList;
	import mx.collections.IList;
	import mx.utils.ArrayUtil;
	
	import org.hamster.common.utils.StringUtil;
	
	[Event(name="parallelEventItemSuccess", type="org.hamster.common.puremvc.events.ParallelEvent")]
	[Event(name="parallelEventItemFailure", type="org.hamster.common.puremvc.events.ParallelEvent")]
	[Event(name="parallelEventAllFinished", type="org.hamster.common.puremvc.events.ParallelEvent")]
	[Event(name="parallelEventAllFailure", type="org.hamster.common.puremvc.events.ParallelEvent")]
	
	public class ParallelEventHandler extends EventDispatcher
	{
		private const _targetList:ArrayCollection = new ArrayCollection();
		
		public var quitWhenFailed:Boolean = false;
		
		public function ParallelEventHandler()
		{
			super();
		}
		
		public function addChild(target:IEventDispatcher, 
								 successEventName:*, 
								 failureEventName:*):void
		{
			if (target == null) {
				throw new Error("Target is null.");
			}
			
			var detailVO:ParallelEventDetailVO = new ParallelEventDetailVO();
			detailVO.target = target;
			detailVO.successEventName = successEventName;
			detailVO.failureEventName = failureEventName;
			var eventName:String;
			registerEvents(detailVO);
			
			this._targetList.addItem(detailVO);
		}
		
		public function removeChild(target:IEventDispatcher):IEventDispatcher
		{
			for each (var child:ParallelEventDetailVO in this._targetList) {
				if (child.target == target) {
					this._targetList.removeItemAt(_targetList.getItemIndex(child));
					return child.target;
				}
			}
			return null;
		}
		
		public function removeChildAt(index:int):IEventDispatcher
		{
			return this.removeChild(this.getChildAt(index));
		}
		
		public function getChildIndex(target:IEventDispatcher):int
		{
			for (var i:int = 0; i < _targetList.length; i++) {
				var detailVO:ParallelEventDetailVO = _targetList[i];
				if (detailVO.target == target) {
					return i;
				}
			}
			return -1;
		}
		
		public function getChildAt(index:int):IEventDispatcher
		{
			return _targetList[index].target as IEventDispatcher;
		}
		
		public function removeAll():void
		{
			_targetList.removeAll();
		}
		
		private function registerEvents(targetDetailVO:ParallelEventDetailVO):void
		{
			var successEventName:* = targetDetailVO.successEventName;
			var failureEventName:* = targetDetailVO.failureEventName;
			var eventName:String;
			var target:IEventDispatcher = targetDetailVO.target;
			
			if (successEventName is Array || successEventName is IList) {
				for each (eventName in successEventName) {
					target.addEventListener(eventName, successEventHandler, false, -100);
				}
			} else {
				target.addEventListener(successEventName, successEventHandler, false, -100);
			}
			
			if (failureEventName is Array || failureEventName is IList) {
				for each (eventName in failureEventName) {
					target.addEventListener(eventName, failureEventHandler, false, -100);
				}
			} else {
				target.addEventListener(failureEventName, failureEventHandler, false, -100);
			}
		}
		
		private function removeEvents(targetDetailVO:ParallelEventDetailVO):void
		{
			var successEventName:* = targetDetailVO.successEventName;
			var failureEventName:* = targetDetailVO.failureEventName;
			var eventName:String;
			var target:IEventDispatcher = targetDetailVO.target;
			
			if (successEventName is Array || successEventName is IList) {
				for each (eventName in successEventName) {
					target.removeEventListener(eventName, successEventHandler);
				}
			} else {
				target.removeEventListener(successEventName, successEventHandler);
			}
			
			if (failureEventName is Array || failureEventName is IList) {
				for each (eventName in failureEventName) {
					target.removeEventListener(eventName, failureEventHandler);
				}
			} else {
				target.removeEventListener(failureEventName, failureEventHandler);
			}
		}
		
		
		private function successEventHandler(event:Event):void
		{
			var detailVO:ParallelEventDetailVO = _targetList.getItemAt(this.getChildIndex(event.currentTarget as IEventDispatcher)) as ParallelEventDetailVO;
			this.removeEvents(detailVO);
			detailVO.result = event.type;
			var parallelEvent:ParallelEvent = new ParallelEvent(ParallelEvent.ITEM_SUCCESS, detailVO);
			parallelEvent.relatedEvent = event;
			this.dispatchEvent(parallelEvent);
			if (isAllDone()) {
				this.dispatchEvent(new ParallelEvent(ParallelEvent.ALL_FINISHED));
			}
		}
		
		private function failureEventHandler(event:Event):void
		{
			var detailVO:ParallelEventDetailVO = _targetList.getItemAt(this.getChildIndex(event.currentTarget as IEventDispatcher)) as ParallelEventDetailVO;
			this.removeEvents(detailVO);
			detailVO.result = event.type;
			var parallelEvent:ParallelEvent = new ParallelEvent(ParallelEvent.ITEM_FAILURE, detailVO);
			parallelEvent.relatedEvent = event;
			this.dispatchEvent(parallelEvent);
			if (this.quitWhenFailed || isAllFailed()) {
				this.dispatchEvent(new ParallelEvent(ParallelEvent.ALL_FAILURE));
			} else if (isAllDone()) {
				this.dispatchEvent(new ParallelEvent(ParallelEvent.ALL_FINISHED));
			}
		}
		
		private function isAllDone():Boolean
		{
			for each (var detailVO:ParallelEventDetailVO in this._targetList) {
				if (StringUtil.isEmpty(detailVO.result)) {
					return false;
				}
			}
			return true;
		}
		
		private function isAllFailed():Boolean
		{
			for each (var detailVO:ParallelEventDetailVO in this._targetList) {
				if (StringUtil.isEmpty(detailVO.result)) {
					return false;
				}
				if (detailVO.failureEventName is Array || detailVO.failureEventName is IList) {
					for each (var eventName:String in detailVO.successEventName) {
						if (detailVO.result == eventName) {
							return false;
						}
					}
				} else if (detailVO.successEventName == detailVO.result) {
					return false;
				}
			}
			return true;
		}
	}
}
